package crmdna.payment;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import paypalnvp.core.PayPal;
import paypalnvp.core.PayPal.Environment;
import paypalnvp.fields.PaymentAction;
import paypalnvp.fields.PaymentItem;
import paypalnvp.profile.BaseProfile;
import paypalnvp.profile.Profile;
import paypalnvp.request.DoExpressCheckoutPayment;
import paypalnvp.request.GetExpressCheckoutDetails;
import paypalnvp.request.SetExpressCheckout;
import crmdna.client.Client;
import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.Utils;
import crmdna.common.Utils.PaypalErrorType;
import crmdna.program.Program;
import crmdna.program.ProgramEntity;
import crmdna.registration.Invoice;
import crmdna.registration.Registration;
import crmdna.registration.Registration.RegistrationStatus;
import crmdna.registration.RegistrationProp;

public class Payment {

	public static String setExpressCheckoutAndGetPaymentUrl(String client,
			String email, String paypalPaymentName, String invoiceNo,
			String login, String pwd, String secret, double cost,
			String currency, String rootUrl, boolean sandbox) {

		Client.ensureValid(client);
		Utils.ensureValidEmail(email);

		ensureValidPaypalApiCredentials(login, pwd, secret);

		if ((null == paypalPaymentName) || paypalPaymentName.equals(""))
			Utils.throwIncorrectSpecException("Paypal payment name not specified");

		// TODO: validate invoice number

		PaymentItem paymentItem = new PaymentItem();

		paymentItem.setAmount(Utils.asCurrencyString(cost));
		paymentItem.setName(paypalPaymentName);

		PaymentItem[] paymentItems = { paymentItem };
		paypalnvp.fields.Payment payment = new paypalnvp.fields.Payment(
				paymentItems);
		payment.setInvoiceNumber(invoiceNo);
		// set client as custom field
		payment.setCustomField(client);
		payment.setCurrency(getPaypalCurrencyEnum(currency));

		String returnUrl = rootUrl + "/doExpressCheckout";
		String cancelUrl = returnUrl;
		SetExpressCheckout setExpressCheckout = new SetExpressCheckout(payment,
				returnUrl, cancelUrl);
		setExpressCheckout.setEmail(email);

		PayPal payPal = getPayPalConnection(login, pwd, secret, sandbox);
		payPal.setResponse(setExpressCheckout);

		Map<String, String> response = setExpressCheckout.getNVPResponse();

		Logger logger = Logger.getLogger(Payment.class.getName());
		logger.info("SetExpressCheckout response: " + response.toString());

		if (!"Success".equals(response.get("ACK"))) {

			long registrationId = Invoice.getRegistrationNo(invoiceNo);

			Registration.handlePaypalError(client, registrationId,
					PaypalErrorType.PAYPAL_SET_EXPRESS_CHECKOUT_FAILURE,
					response.get("L_ERRORCODE0"),
					response.get("L_LONGMESSAGE0"),
					response.get("L_SEVERITYCODE0"),
					response.get("L_SHORTMESSAGE0"), null); // null for no
															// redirect

			String errorMessage = getErrMessageFromNVPResponse(
					PaypalErrorType.PAYPAL_SET_EXPRESS_CHECKOUT_FAILURE,
					response);

			logger.warning(errorMessage);
			throw new APIException(Status.PAYPAL_ERROR, errorMessage);
		}

		// set express checkout is successful
		String token = response.get("TOKEN");

		if ((token == null) || token.equals("")) {
			throw new APIException(Status.PAYPAL_ERROR, "Invalid token ["
					+ token + "] after call to setExpressCheckout");
		}

		long registrationId = Invoice.getRegistrationNo(invoiceNo);

		TokenProp tokenProp = new TokenProp();
		tokenProp.client = client;
		tokenProp.paypalLogin = login;
		tokenProp.paypalPwd = pwd;
		tokenProp.paypalSecret = secret;
		tokenProp.registrationId = registrationId;
		tokenProp.sandbox = sandbox;
		tokenProp.token = token;

		Token.save(tokenProp);

		String paymentUrl = getPaypalPaymentUrlFromToken(token, sandbox);
		Utils.ensureValidUrl(paymentUrl);
		return paymentUrl;
	}

	static String doExpressCheckout(String token, String payerId, String login,
			String pwd, String secret, String rootUrl, boolean sandbox) {

		if ((token == null) || token.equals(""))
			throw new APIException(Status.PAYPAL_ERROR, "Paypal token in null");

		TokenProp tokenProp = Token.safeGet(token);

		long registrationId = tokenProp.registrationId;
		String client = tokenProp.client;

		RegistrationProp registrationProp = Registration.safeGet(client,
				registrationId).toProp();
		String successRedirectUrl = registrationProp.successCallbackUrl;
		String errorRedirectUrl = registrationProp.errorCallbackUrl;

		PayPal payPal = getPayPalConnection(login, pwd, secret, sandbox);

		GetExpressCheckoutDetails checkoutDetails = new GetExpressCheckoutDetails(
				token);
		payPal.setResponse(checkoutDetails);
		Map<String, String> response = checkoutDetails.getNVPResponse();

		Logger logger = Logger.getLogger(Payment.class.getName());
		logger.info("Get express checkout response: " + response.toString());

		if (!"Success".equals(response.get("ACK"))) {
			String errMessage = getErrMessageFromNVPResponse(
					PaypalErrorType.PAYPAL_GET_EXPRESS_CHECKOUT_FAILURE,
					response);

			logger.severe(errMessage);

			Map<String, Object> map = new HashMap<>();
			map.put("status",
					PaypalErrorType.PAYPAL_GET_EXPRESS_CHECKOUT_FAILURE);
			map.put("token", token);
			map.put("L_ERRORCODE0", response.get("L_ERRORCODE0"));
			map.put("L_SEVERITYCODE0", response.get("L_SEVERITYCODE0"));
			map.put("L_SHORTMESSAGE0", response.get("L_SHORTMESSAGE0"));
			map.putAll(getProgramDetailsAsMap(client,
					registrationProp.programId));

			String redirectUrl = Utils.getUrl(errorRedirectUrl, map);
			Registration.handlePaypalError(tokenProp.client,
					tokenProp.registrationId,
					PaypalErrorType.PAYPAL_GET_EXPRESS_CHECKOUT_FAILURE,
					response.get("L_ERRORCODE0"),
					response.get("L_LONGMESSAGE0"),
					response.get("L_SEVERITYCODE0"),
					response.get("L_SHORTMESSAGE0"), redirectUrl);

			return redirectUrl;
		}

		logger.info("Get express checkout success for token [" + token + "]");

		String invoiceNo = response.get("INVNUM");

		Client.safeGet(client);

		PaymentResult paymentResult = new PaymentResult();
		paymentResult.isPaymentAuthorized = false;
		paymentResult.invoiceNo = invoiceNo;
		paymentResult.registrationId = registrationId;
		paymentResult.client = client;
		paymentResult.isPaymentPending = true;

		if ((payerId == null) || payerId.equals("")) {
			Map<String, Object> map = new HashMap<>();
			map.put("status", RegistrationStatus.PAYMENT_NOT_AUTHORIZED);
			map.put("registrationId", new Long(registrationId));
			map.put("invoiceNo", invoiceNo);

			map.putAll(getProgramDetailsAsMap(client,
					registrationProp.programId));

			String redirectUrl = Utils.getUrl(errorRedirectUrl, map);

			Registration.handlePaymentAuthorizationFailure(client,
					registrationId, redirectUrl);

			return redirectUrl;
		}

		logger.info("Get express checkout success for token [" + token
				+ "], INVNUM = [" + invoiceNo + "], payerId = [" + payerId
				+ "]");

		// payment is authorized
		Registration.handlePaymentAuthorization(client, registrationId);

		PaymentItem item = new PaymentItem();
		item.setAmount(response.get("AMT"));

		PaymentItem[] items = { item };
		paypalnvp.fields.Payment payment = new paypalnvp.fields.Payment(items);
		payment.setCurrency(getPaypalCurrencyEnum(response.get("CURRENCYCODE")));

		payment.setNotifyUrl(rootUrl + "/payment/ipn");

		DoExpressCheckoutPayment doExpressCheckout = new DoExpressCheckoutPayment(
				payment, token, PaymentAction.SALE, payerId);
		payPal.setResponse(doExpressCheckout);
		response = doExpressCheckout.getNVPResponse();
		logger.info("Do express checkout response: " + response.toString());

		if (!"Success".equals(response.get("ACK"))) {
			String errMessage = getErrMessageFromNVPResponse(
					PaypalErrorType.PAYPAL_DO_EXPRESS_CHECKOUT_FAILURE,
					response);
			logger.severe(errMessage);

			Map<String, Object> map = new HashMap<>();
			map.put("status",
					PaypalErrorType.PAYPAL_DO_EXPRESS_CHECKOUT_FAILURE);
			map.put("token", token);
			map.put("registrationId", new Long(registrationId));
			map.put("invoiceNo", invoiceNo);
			map.put("L_ERRORCODE0", response.get("L_ERRORCODE0"));
			map.put("L_SEVERITYCODE0", response.get("L_SEVERITYCODE0"));
			map.put("L_SHORTMESSAGE0", response.get("L_SHORTMESSAGE0"));

			map.putAll(getProgramDetailsAsMap(client,
					registrationProp.programId));

			String redirectUrl = Utils.getUrl(errorRedirectUrl, map);

			Registration.handlePaypalError(tokenProp.client,
					tokenProp.registrationId,
					PaypalErrorType.PAYPAL_GET_EXPRESS_CHECKOUT_FAILURE,
					response.get("L_ERRORCODE0"),
					response.get("L_LONGMESSAGE0"),
					response.get("L_SEVERITYCODE0"),
					response.get("L_SHORTMESSAGE0"), redirectUrl);

			return redirectUrl;
		}

		String transactionId = response.get("TRANSACTIONID");

		// do express checkout is successful, payment could still be pending
		Map<String, Object> map = new HashMap<>();
		map.put("transactionId", transactionId);
		map.put("amount", response.get("AMT"));
		map.put("ccy", response.get("CURRENCYCODE"));
		map.put("email", registrationProp.email);

		map.putAll(getProgramDetailsAsMap(client, registrationProp.programId));

		boolean isPaymentPending = false;
		if (response.get("PAYMENTSTATUS").equals("Pending")) {
			isPaymentPending = true;
			map.put("status", RegistrationStatus.PAYMENT_PENDING.toString());
			map.put("pendingReason", response.get("PENDINGREASON"));
		} else
			map.put("status",
					RegistrationStatus.REGISTRATION_COMPLETE.toString());

		String redirectUrl = Utils.getUrl(successRedirectUrl, map);

		Utils.ensureValidUrl(redirectUrl);

		Registration.handleDoExpressCheckoutResponse(client, registrationId,
				response.get("AMT"), response.get("CURRENCYCODE"),
				transactionId, isPaymentPending, response.get("PENDINGREASON"),
				redirectUrl);

		return redirectUrl;
	}

	private static Map<String, Object> getProgramDetailsAsMap(String client,
			long programId) {

		ProgramEntity programEntity = Program.get(client, programId);
		if (null == programEntity) {
			Logger logger = Logger.getLogger(Payment.class.getName());
			logger.severe("ProgramId [" + programId + "] not found");
			return new HashMap<>();
		}

		return programEntity.toProp().asMap();
	}

	static String getPaypalPaymentUrlFromToken(String token, boolean sandbox) {

		StringBuilder builder = new StringBuilder();
		builder.append("https://www.");
		if (sandbox == true)
			builder.append("sandbox.");

		builder.append("paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=");
		builder.append(token);

		return builder.toString();
	}

	static PayPal getPayPalConnection(String login, String pwd, String secret,
			boolean sandbox) {

		ensureValidPaypalApiCredentials(login, pwd, secret);

		Environment env = Environment.SANDBOX;
		if (!sandbox)
			env = Environment.LIVE;

		Profile user = new BaseProfile.Builder(login, pwd).signature(secret)
				.build();

		PayPal paypal = new PayPal(user, env);

		return paypal;
	}

	static paypalnvp.fields.Currency getPaypalCurrencyEnum(String currency) {
		if (currency == null)
			Utils.throwIncorrectSpecException("Currency is null");

		currency = currency.toUpperCase();

		if (currency.equals("SGD"))
			return paypalnvp.fields.Currency.SGD;

		if (currency.equals("AUD"))
			return paypalnvp.fields.Currency.AUD;

		if (currency.equals("USD"))
			return paypalnvp.fields.Currency.USD;

		if (currency.equals("GBP"))
			return paypalnvp.fields.Currency.GBP;

		if (currency.equals("MYR"))
			return paypalnvp.fields.Currency.MYR;

		Utils.throwIncorrectSpecException("Payment not supported for currency ["
				+ currency + "]");

		return null; // should never reach here
	}

	static void ensureValidPaypalApiCredentials(String login, String pwd,
			String secret) {

		if ((null == login) || login.equals(""))
			Utils.throwIncorrectSpecException("Paypal login is not specified");

		if ((null == pwd) || pwd.equals(""))
			Utils.throwIncorrectSpecException("Paypal password is not specified");

		if ((null == secret) || secret.equals(""))
			Utils.throwIncorrectSpecException("Paypal secret is not specified");
	}

	private static String getErrMessageFromNVPResponse(
			PaypalErrorType paypalErrorType, Map<String, String> response) {
		String errorMessage = paypalErrorType + " - " + "L_SEVERITYCODE0: "
				+ response.get("L_SEVERITYCODE0") + ", L_ERRORCODE0: "
				+ response.get("L_ERRORCODE0") + ", L_SHORTMESSAGE0: "
				+ response.get("L_SHORTMESSAGE0") + ", L_LONGMESSAGE0: "
				+ response.get("L_LONGMESSAGE0");

		return errorMessage;
	}
}
